﻿using System;
using System.Globalization;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.UI;
using DnnMvcBridge.Web;
using DotNetNuke.UI.Modules;

namespace DnnMvcBridge
{
	public abstract class MvcModuleApplication : ModuleControlBase, IModuleControl
	{
		private ModuleInstanceContext _moduleContext;

		protected abstract string ControllerName { get; set; }
		protected abstract string ActionName { get; set; }
		protected abstract string[] ControllerNamespaces { get; }
		protected abstract void MapModuleRoutes(RouteCollection routes);
		protected abstract string FolderPath { get; }
		
		#region Constants
		
		private const string ControllerMasterFormat = "~/DesktopModules/{0}/Views/{{1}}/{{0}}.cshtml";
		private const string SharedMasterFormat = "~/DesktopModules/{0}/Views/Shared/{{0}}.cshtml";
		private const string ControllerViewFormat = "~/DesktopModules/{0}/Views/{{1}}/{{0}}.cshtml";
		private const string SharedViewFormat = "~/DesktopModules/{0}/Views/Shared/{{0}}.cshtml";
		private const string ControllerPartialFormat = "~/DesktopModules/{0}/Views/{{1}}/{{0}}.cshtml";
		private const string SharedPartialFormat = "~/DesktopModules/{0}/Views/Shared/{{0}}.cshtml";

		#endregion

		protected override void OnInit(EventArgs e)
		{
				// do something here if you like...
		}

		public override void RenderControl(HtmlTextWriter writer)
		{
			var actionResultContent = RenderMvcAction();
			writer.WriteLine(actionResultContent);
		}
		
		private IHtmlString RenderMvcAction()
		{

			InitViewEngine(FolderPath);

			var parentContext = HttpContext.Current.Request.RequestContext;

			if (parentContext != null)
			{
				var controllerName = GetControllerName(parentContext);
				var actionName = GetActionName(parentContext);
				using (var capture = new CaptureContext(parentContext.HttpContext))
				{
					var routedata = new RouteData(parentContext.RouteData.Route, parentContext.RouteData.RouteHandler);

					if (routedata.Values.ContainsKey("controller"))
					{

						routedata.Values["controller"] = controllerName;
					}
					else
					{
						routedata.Values.Add("controller", controllerName);
					}

					if (routedata.Values.ContainsKey("action"))
					{

						routedata.Values["action"] = actionName;
					}
					else
					{
						routedata.Values.Add("action", actionName);
					}
					
					// set the namespaces or other DNN modules using the MVC framework will give error about finding multiple controllers with the same name
					routedata.DataTokens.Add("namespaces", ControllerNamespaces);
					
					// Note that because this MVC app runs as a DNN module the route config  does not run as you would expect
					// we have to create a default route so that Html.RenderAction and Html.Action as well as other route dependant features will work.
					// call the MapRoutes function implemented by the derived class that creates route(s) for the module instance, the typical default route is all you should need for DNN Modules
					MapModuleRoutes(RouteTable.Routes);
					
					var httpContext = new MvcModuleHttpContextWrapper(HttpContext.Current);
					var context = new RequestContext(httpContext, routedata);
					
					var controllerfactory = System.Web.Mvc.ControllerBuilder.Current.GetControllerFactory();
					var controllerInstance = controllerfactory.CreateController(context, controllerName) as System.Web.Mvc.ControllerBase;

					if (controllerInstance == null)
					{
						throw new InvalidOperationException("controller Instance is null");
					}
					
					// here we will make the executing module and page instances available to the mvc application being wrapped by this bridge
					controllerInstance.ViewData["ModuleContext"] = ModuleContext;
					controllerInstance.ViewData["Page"] = Page;
					
					try
					{
						((System.Web.Mvc.IController)controllerInstance).Execute(context);
						
					}
					catch (Exception ex)
					{
						capture.CaptureOutput.Write(ex.Message);
					}
					finally
					{

						controllerfactory.ReleaseController((System.Web.Mvc.IController)controllerInstance);
					}

					return new HtmlString(capture.GetOutput());
				}
			}
			else
			{

				return new HtmlString("<div>DnnMvcBridge: Unable to get the Request Context.</div>");
			}
			
		}

		private static void InitViewEngine(string folderPath)
		{

			string prefix = NormalizeFolderPath(folderPath);
			string[] masterFormats = new[] { 
                String.Format(CultureInfo.InvariantCulture, ControllerMasterFormat, prefix),
                String.Format(CultureInfo.InvariantCulture, SharedMasterFormat, prefix) };
			string[] viewFormats = new[] { 
                String.Format(CultureInfo.InvariantCulture, ControllerViewFormat, prefix),
                String.Format(CultureInfo.InvariantCulture, SharedViewFormat, prefix),
                String.Format(CultureInfo.InvariantCulture, ControllerPartialFormat, prefix),
                String.Format(CultureInfo.InvariantCulture, SharedPartialFormat, prefix) };

			//Configure the View Engines
			ViewEngines.Engines.Clear();
			ViewEngines.Engines.Add(new RazorViewEngine()
			{
				MasterLocationFormats = masterFormats,
				ViewLocationFormats = viewFormats,
				PartialViewLocationFormats = viewFormats
			});

		}

		private static string NormalizeFolderPath(string path)
		{
			// Remove leading and trailing slashes
			if (!String.IsNullOrEmpty(path))
			{
				return path.Trim('/');
			}
			return path;
		}
		
		private string GetControllerName(System.Web.Routing.RequestContext context)
		{
			string controller;
			// use the specified value from the derived class
			if (!string.IsNullOrEmpty(ControllerName))
			{
				controller = ControllerName;
			}
			else
			{
				// else look for controller in the url
				controller = context.HttpContext.Request.Params["controller"];
				if (string.IsNullOrEmpty(controller))
				{
					// not found? then use default
					controller = "Home"; 
				}

				controller = controller.Trim();

			}

			return controller;
		}

		private string GetActionName(System.Web.Routing.RequestContext context)
		{
			string actionName;
			// use the specified value from the derived class
			if (!string.IsNullOrEmpty(ActionName))
			{
				actionName = ActionName;
			}
			else
			{
				// else look for action in the url
				actionName = context.HttpContext.Request.Params["action"];
				if (string.IsNullOrEmpty(actionName))
				{
					// not found? then use default
					actionName = "Index";
				}

				actionName = actionName.Trim();
			}
			
			return actionName;
		}
		
		#region IModuleControl Implementation

		public new Control Control
		{
			get { throw new System.NotImplementedException(); }
		}

		public new string ControlPath
		{
			get { throw new System.NotImplementedException(); }
		}

		public new string ControlName
		{
			get { throw new System.NotImplementedException(); }
		}

		public new string LocalResourceFile
		{
			get { return string.Empty; }
			set { var notUsedForAnything = value; }
		}

		public new ModuleInstanceContext ModuleContext
		{
			get { return _moduleContext ?? (_moduleContext = new ModuleInstanceContext(this)); }
		}

		#endregion

	}
}
